package com.ivan.toolkit.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

import com.google.common.collect.Maps;

import lombok.Getter;

public class Https {
	
	
	
	@FunctionalInterface
	public interface HttpFunction<T, R> {

	    /**
	     * Applies this function to the given argument.
	     *
	     * @param t the function argument
	     * @return the function result
	     */
	    R apply(T t) throws Exception;
	}
	
	@FunctionalInterface
	public interface HttpConsumer<T> {

	    /**
	     * Performs this operation on the given argument.
	     *
	     * @param t the input argument
	     */
	    void accept(T t) throws Exception;
	}
		
	public static class HttpURLConnectionConfig {
		
		private HttpURLConnection connection = null;

		private HttpURLConnectionConfig(HttpURLConnection connection) {
			super();
			this.connection = connection;
			
			// default
			try {
				this
				.setMethodGET()
				.setConnectTimeout(5000)
				.setReadTimeout(5000)
				.setUseCaches(false);				
			} catch(Throwable t) {};
		}
		
		public HttpURLConnectionConfig setMethodPOST() throws ProtocolException {
			connection.setRequestMethod("POST");
			return this;
		}
		
		public HttpURLConnectionConfig setMethodGET() throws ProtocolException {
			connection.setRequestMethod("GET");
			return this;
		}
		
		public HttpURLConnectionConfig setMethodPUT() throws ProtocolException {
			connection.setRequestMethod("PUT");
			return this;
		}
		
		public HttpURLConnectionConfig setMethodDELETE() throws ProtocolException {
			connection.setRequestMethod("DELETE");
			return this;
		}
		
		public HttpURLConnectionConfig setConnectTimeout(int timeout) {
			connection.setConnectTimeout(timeout);
			return this;
		}
		
		public HttpURLConnectionConfig setReadTimeout(int timeout) {
			connection.setReadTimeout(timeout);
			return this;
		}
		
		public HttpURLConnectionConfig setDoOutput(boolean dooutput) {
			connection.setDoOutput(dooutput);
			return this;
		}
		
		public HttpURLConnectionConfig setDoInput(boolean doinput) {
			connection.setDoInput(doinput);
			return this;
		}
		
		public HttpURLConnectionConfig setUseCaches(boolean usecaches) {
			connection.setUseCaches(usecaches);
			return this;
		}
		
		public HttpURLConnectionConfig setInstanceFollowRedirects(boolean followRedirects) {
			connection.setInstanceFollowRedirects(followRedirects);
			return this;
		}
		
		public HttpURLConnectionConfig setRequestProperty(String key, String value) {
			connection.setRequestProperty(key, value);
			return this;
		}
		
		public HttpURLConnectionConfig addRequestProperty(String key, String value) {
			connection.addRequestProperty(key, value);
			return this;
		}
		
		public HttpURLConnectionConfig setCharset(String charset) {
			connection.setRequestProperty("Charset", charset);
			return this;
		}
		
		public HttpURLConnectionConfig setKeepAlive() {
			connection.setRequestProperty("Connection", "Keep-Alive");
			return this;
		}
		
		public HttpURLConnectionConfig setContentType(String type) {
			connection.setRequestProperty("Content-Type", type);
			return this;
		}
		
		public HttpURLConnectionConfig setContentTypeMultipart_form_data(String boundary) {
			connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
			return this;
		}
		
		public HttpURLConnectionConfig setContentTypeJson() {
			connection.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
			return this;
		}
		
		public HttpURLConnectionConfig setContentTypeX_www_form_urlencoded() {
			connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); 
			return this;
		}
		
		public HttpURLConnectionConfig connect() throws IOException {
			connection.connect();
			return this;
		}
		
		public HttpURLConnectionConfig doOutput(Consumer<OutputStream> consumer) throws IOException {
			
			if(consumer != null) {
				OutputStream out = connection.getOutputStream();
				consumer.accept(out);
				out.flush();
				out.close();
			}
			
			return this;
		}
		
		public <R> R finish(HttpFunction<InputStream, R> function) throws Exception {
			
			try {
				
				int responseCode = connection.getResponseCode();
				if(responseCode > 300) {
					throw new Exception(
							"HTTP Request is not success, Response code is " + responseCode);
				}
				
				if(HttpURLConnection.HTTP_OK == responseCode) {
					
					if(function != null) {
						
						InputStream in = connection.getInputStream();
						return function.apply(in);
					}
					
				}
				
				return null;
				
			} finally {
				
				
			}

		}
		
	}
	
	
	public static class HttpURLConnectionOutput {
		
		@Getter
		private HttpURLConnection connection = null;
		
		@Getter
		private OutputStream outputStream = null;

		private HttpURLConnectionOutput(HttpURLConnection connection, OutputStream outputStream) {
			super();
			this.connection = connection;
			this.outputStream = outputStream;
		}
		
		public void writeX_www_form_urlencoded(String charset, String...args) throws IOException {
			NameValuePairs nv = new NameValuePairs();
			nv.parse(args);
			nv.writeEncodedTo(outputStream, charset);
		}
		
		public void writeX_www_form(String...args) throws IOException {
			NameValuePairs nv = new NameValuePairs();
			nv.parse(args);
			nv.writeTo(outputStream);
		}

	}
	
	public static class HttpURLConnectionInput {
		
		@Getter
		private HttpURLConnection connection = null;
		
		@Getter
		private InputStream inputStream = null;

		private HttpURLConnectionInput(HttpURLConnection connection, InputStream inputStream) {
			super();
			this.connection = connection;
			this.inputStream = inputStream;
		}

	}
	
	public static HttpURLConnectionAdapt getAdapt(String url, String...args) {
		HttpURLConnectionAdapt adapt = new HttpURLConnectionAdapt();
		adapt.setUrl(url, args);
		return adapt;
	}
	
	public static class HttpURLConnectionAdapt {
		
		public HttpURLConnectionAdapt() {
		}
		
		private Supplier<String> urlSupplier;
		private HttpURLConnectionAdapt setUrlSupplier(Supplier<String> supplier) {
			this.urlSupplier = supplier;
			return this;
		}	
		private HttpURLConnectionAdapt setUrl(String url, String...args) {
			setUrlSupplier(()-> {
				NameValuePairs nv = new NameValuePairs();
				nv.parse(args);
				return nv.buildUrl(url);
			});
			return this;
		}

		private HttpConsumer<HttpURLConnectionConfig> configConsumer;	
		public HttpURLConnectionAdapt onConfig(HttpConsumer<HttpURLConnectionConfig> consumer) {
			this.configConsumer = consumer;
			return this;
		}
		
		private HttpConsumer<HttpURLConnectionOutput> outputConsumer;	
		public HttpURLConnectionAdapt onOutput(HttpConsumer<HttpURLConnectionOutput> consumer) {
			this.outputConsumer = consumer;
			return this;
		}
		
		private HttpURLConnection commonHandle() throws Exception {
			
			if(urlSupplier == null) 
				throw new Exception("Url supplier not found");

			String urlString = this.urlSupplier.get();
			
			URL url = new URL(urlString); 
			HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
			  
	        urlConnection.setDoOutput(true);  
	        urlConnection.setDoInput(true);  
	        urlConnection.setUseCaches(false);
			if(configConsumer != null) {
				configConsumer.accept(new HttpURLConnectionConfig(urlConnection));
			}
			
			urlConnection.connect();
			
			if(this.outputConsumer != null) {
		
				OutputStream out = urlConnection.getOutputStream();
				this.outputConsumer.accept(new HttpURLConnectionOutput(urlConnection, out));
				out.flush();
				out.close();
			}
			
			int responseCode = urlConnection.getResponseCode();
			if(responseCode > 300) {
				throw new Exception(
						"HTTP Request is not success, Response code is " + responseCode);

			}
			
			return urlConnection;
			
		}
		
		public void finish(HttpConsumer<HttpURLConnectionInput> consumer) throws Exception {
			
			HttpURLConnection urlConnection = commonHandle();

			if(HttpURLConnection.HTTP_OK == urlConnection.getResponseCode()) {
				
				if(consumer != null) {
					InputStream in = urlConnection.getInputStream();
					consumer.accept(new HttpURLConnectionInput(urlConnection, in));
					in.close();
				}
				
			}
			
			urlConnection.disconnect();
			urlConnection = null;
			
		}
		
		public <R> R finish(HttpFunction<HttpURLConnectionInput, R> function) throws Exception {
			
			HttpURLConnection urlConnection = commonHandle();
			
			R result = null;
			
			if(HttpURLConnection.HTTP_OK == urlConnection.getResponseCode()) {
				
				if(function != null) {
					
					InputStream in = urlConnection.getInputStream();
					result = function.apply(new HttpURLConnectionInput(urlConnection, in));
					in.close();
				}
				
			}
			
			urlConnection.disconnect();
			urlConnection = null;
			
			return result;
		}
		
	}
	
	static class NameValuePairs {
		
		private Map<String, String> map = Maps.newHashMap();
		
		public NameValuePairs set(String key, String value) {
			map.put(key, value);
			return this;
		}
		
		public NameValuePairs parse(String... args) {
			Iterator<String> it = Arrays.asList(args).iterator();
			while(it.hasNext()) {
				
				String key = it.next();
				
				if(!it.hasNext()) {
					break;
				}
				
				String value = it.next();
				map.put(key, value);
			}
			return this;
		}
		
		public String buildUrl(String urlString) {
            StringBuffer param = new StringBuffer();  
            int i = 0;  
            for (String key : map.keySet()) {  
                if (i == 0)  
                    param.append("?");  
                else  
                    param.append("&");  
                param.append(key).append("=").append(map.get(key));  
                i++;  
            }
            return urlString + param;
		}
		
		public void writeTo(OutputStream os) throws IOException {
            StringBuffer param = new StringBuffer();  
            for (String key : map.keySet()) {
                param.append("&").append(key).append("=").append(map.get(key));  
            }
            os.write(param.toString().getBytes());
		}
		
		public void writeEncodedTo(OutputStream os, String charset) throws IOException {
            StringBuffer param = new StringBuffer();  
            for (String key : map.keySet()) {
                param
                .append("&")
                .append(URLEncoder.encode(key, charset))
                .append("=")
                .append(URLEncoder.encode(map.get(key), charset));  
            }
            os.write(param.toString().getBytes());
		}
		
	}
	

}
